#!/usr/bin/env python
# _*_ coding: UTF-8 _*_

import os, sys, signal, pty
from SSHBatch.Log import Log
from SSHBatch.Reactor import Reactor

class SSHAgent(object):
    def __init__(self, hostid, ctx):
        # properties changed runtime
        self.reset()
        
        # properties only initiated at constructure
        self.sshconfig = ctx["config"]["Ssh"]
        self.hostconfig = ctx["config"]["Hosts"][hostid]
        self.logger = ctx["logger"]
        self.reactor = ctx["reactor"]
        self.hostid = hostid
        self.numopens = 0
        self.linesize_hiwater = 4096
        self.linesize_lowater = 1024
        self._capbuffer = ""
        self._capture = False
        self._focus = False
    
    def __del__(self):
        self.close()
    
    def reset(self):
        self.rcvlines = []
        self.sndbuffer = ""
        self.readable = False
        self.writable = False
        self.pid = None
        self.fd = None
    
    def alive(self):
        return self.fd != None

    def getFocus(self):
        return self._focus
    
    def setFocus(self, value):
        self._focus = True if value else False

    focus = property(fget = lambda self:self.getFocus(),
        fset = lambda self,value:self.setFocus(value))

    def getCapture(self):
        return self._capture
    
    def setCapture(self, value):
        self._capture = True if value else False
        # start a new capture
        if self._capture:
            self._capbuffer = ""

    capture = property(fget = lambda self:self.getCapture(),
        fset = lambda self,value:self.setCapture(value))

    def getCaptureBuffer(self):
        return self._capbuffer

    captureBuffer = property(fget = lambda self:self.getCaptureBuffer())

    def open(self, force = False):
        if not force and not self.fd and self.numopens > 0:
            return False
        if not self.fd:
            (pid, fd) = pty.fork()
            if not pid:
                cmdline = [self.sshconfig["path"], "sshbatchterm", 
                    "-l", self.hostconfig["user"], 
                    "-p", self.hostconfig["port"],
                    self.hostconfig["hostname"]]
                os.execlp(*cmdline)
                self.logger.log(Log.LGLEVEL_ERROR, "launching ssh, host", self.hostid, cmdline)
                sys.exit(1)
            self.pid = pid
            self.fd = fd
            self.numopens = self.numopens + 1
            self.writable = True
            self.reactor.register(self, Reactor.READ)
        return True
    
    def close(self):
        if self.fd:
            self.reactor.unregister(self, Reactor.READ|Reactor.WRITE)
            self.shutdown()
            
    def last(self, nolines = 2):
        total = len(self.rcvlines)
        if nolines < 0:
            pos = 0
        else:
            pos = total - nolines
            if pos < 0:
                pos = 0
        while pos < total:
            self.logger.write(self.rcvlines[pos])
            pos = pos + 1

    def write(self, buffer):
        if self.writable:
            self.sndbuffer = self.sndbuffer + buffer
            self.reactor.register(self, Reactor.WRITE)
            return len(buffer)
        return 0

    def shutdown(self):
        self.logger.log(Log.LGLEVEL_INFO, "shutdowning, host", self.hostid)
        os.close(self.fd)
        os.kill(self.pid, signal.SIGKILL)
        os.waitpid(self.pid, 0)
        self.reset()

    def fileno(self):
        return self.fd
        
    def handleRead(self):
        try:
            rcvbuffer =  os.read(self.fd, 4096)
        except Exception, e:
            self.logger.log(Log.LGLEVEL_ERROR, "reading ssh, host", self.hostid, str(e))
            rcvbuffer = ""
        if not rcvbuffer:
            self.logger.log(Log.LGLEVEL_INFO, "closing ssh, host", self.hostid)
            self.reactor.unregister(self, Reactor.READ)
            self.readable = False
            if not self.reactor.has(self, Reactor.WRITE):
                self.shutdown()
            return
        
        lines = rcvbuffer.splitlines(True)
        first = True
        for line in lines:
            if first:
                if self.rcvlines and self.rcvlines[-1][-1] == '\n':
                    self.rcvlines[-1] = self.rcvlines[-1] + line
                else:
                    self.rcvlines.append(line)
                first = False
            else:
                self.rcvlines.append(line)
        if len(self.rcvlines) >= self.linesize_hiwater:
            del self.rcvlines[0:(len(self.rcvlines)-self.linesize_lowater)]
        if self._focus:
            self.logger.write(rcvbuffer)
        if self._capture:
            self._capbuffer = self._capbuffer + rcvbuffer
    
    def handleWrite(self):
        if len(self.sndbuffer) == 0: return
        try:
            bsent = os.write(self.fd, self.sndbuffer)
        except e:
            self.reactor.unregister(self, Reactor.WRITE)
            self.writable = False
            if not self.reactor.has(self, Reactor.READ):
                self.shutdown()
            return
        self.sndbuffer = self.sndbuffer[bsent:]
        if len(self.sndbuffer) == 0:
            self.reactor.unregister(self, Reactor.WRITE)
    
    def handleExcept(self):
        assert 0, "BUG!!!"
